﻿using System;
using System.Collections.Generic;

namespace YasharEl.Infrastructure.ServiceBus
{
    /// <summary>
    /// Contains extension methods
    /// </summary>
    public static class ExtensionMethods
    {
        #region Type Extension Methods

        /// <summary>
        /// Useful for finding if a type is (for example) IMessageHandler{T} where T : IMessage.
        /// </summary>
        /// <param name="type"></param>
        /// <param name="openGenericType"></param>
        /// <param name="genericArg"></param>
        /// <returns></returns>
        public static bool IsGenericallyEquivalent(this Type type, Type openGenericType, Type genericArg)
        {
            bool result = false;
            LoopAndAct(type, openGenericType, genericArg, t => result = true);

            return result;
        }

        /// <summary>
        /// Returns the enclosed generic type given that the type is GenericallyEquivalent.
        /// </summary>
        /// <param name="type"></param>
        /// <param name="openGenericType"></param>
        /// <param name="genericArg"></param>
        /// <returns></returns>
        public static Type GetGenericallyContainedType(this Type type, Type openGenericType, Type genericArg)
        {
            Type result = null;
            LoopAndAct(type, openGenericType, genericArg, t => result = t);

            return result;
        }

        /// <summary>
        /// Takes the name of the given type and makes it friendly for serialization
        /// by removing problematic characters.
        /// </summary>
        /// <param name="t"></param>
        /// <returns></returns>
        public static string SerializationFriendlyName(this Type t)
        {
            lock (TypeToNameLookup)
                if (TypeToNameLookup.ContainsKey(t))
                    return TypeToNameLookup[t];

            var args = t.GetGenericArguments();
            if (args != null)
            {
                int index = t.Name.IndexOf('`');
                if (index >= 0)
                {
                    string result = t.Name.Substring(0, index) + "Of";
                    for (int i = 0; i < args.Length; i++)
                    {
                        result += args[i].SerializationFriendlyName();
                        if (i != args.Length - 1)
                            result += "And";
                    }

                    if (args.Length == 2)
                        if (typeof(KeyValuePair<,>).MakeGenericType(args) == t)
                            result = "YasharEl.Infrastructure.ServiceBus." + result;

                    lock (TypeToNameLookup)
                        TypeToNameLookup[t] = result;

                    return result;
                }
            }

            lock (TypeToNameLookup)
                TypeToNameLookup[t] = t.Name;

            return t.Name;
        }

        #endregion

        #region Object Extensions Methods

        /// <summary>
        /// Sets the value of the header for the given key.
        /// </summary>
        /// <param name="msg"></param>
        /// <param name="key"></param>
        /// <param name="value"></param>
        public static void SetHeader(this object msg, string key, string value)
        {
            SetHeaderAction(msg, key, value);
        }

        /// <summary>
        /// Sets the list of sites to where this message should be routed
        /// This method is reserved for the NServiceBus Gateway.
        /// </summary>
        /// <param name="msg"></param>
        /// <param name="value"></param>
        public static void SetDestinationSitesHeader(this object msg, string value)
        {
            msg.SetHeader(Headers.DestinationSites, value);
        }

        #endregion

        #region Private Methods

        private static void LoopAndAct(Type type, Type openGenericType, Type genericArg, Action<Type> act)
        {
            foreach (var i in type.GetInterfaces())
            {
                var args = i.GetGenericArguments();

                if (args.Length == 1)
                    if (genericArg.IsAssignableFrom(args[0]))
                        if (openGenericType.MakeGenericType(args[0]) == i)
                        {
                            act(args[0]);
                            break;
                        }
            }
        }

        #endregion

        #region Private Static Fields

        private static readonly IDictionary<Type, string> TypeToNameLookup = new Dictionary<Type, string>();

        #endregion

        #region Public Actions

        /// <summary>
        /// The action used to set the header in the <see cref="SetHeader"/> method.
        /// </summary>
        public static Action<object, string, string> SetHeaderAction = (x, y, z) =>
        {
            //default to no-op to avoid getting in the way of unittesting
        };

        /// <summary>
        /// The action used to get all the headers for a message.
        /// </summary>
        public static Func<IDictionary<string, string>> GetStaticOutgoingHeadersAction 
        { 
            get; 
            set; 
        }

        #endregion
    }
}
